/*
 * Copyright (c) 2010, Brookhaven National Laboratory
 * All rights reserved.
 * The software in this package is published under the terms
 * of the BSD license a copy of which has been included with
 * this distribution in the license.txt file.
 */


package org.oxtypes.protobuf.impl;

import java.io.IOException;

import com.google.protobuf.Descriptors.*;
import com.google.protobuf.WireFormat.*;
import com.google.protobuf.CodedOutputStream;

import org.oxtypes.DynamicData;

// OxTypes

import com.google.protobuf.PublicWireFormat;

/**
 *
 */
public class RepeatedFieldWriter {

    public RepeatedFieldWriter(){
    }

    public void computeByteSize(DynamicData dd, FieldDescriptor fd,
            LengthDelimitedField ldField){

        int id = fd.getNumber();
        
        DynamicData field = dd.getComplexValue(id);
        int length = field.getLength();
        if(length == 0) return;

        // tag size
        ldField.addSize(length*CodedOutputStream.computeTagSize(id));

        switch(fd.getLiteType()){
            // fixed size types
            case DOUBLE  : ldField.addSize(length*doubleSize); break;
            case FLOAT   : ldField.addSize(length*floatSize);  break;
            case FIXED64 : ldField.addSize(length*fixed64Size); break;
            case FIXED32 : ldField.addSize(length*fixed32Size); break;
            case SFIXED64: ldField.addSize(length*sfixed64Size); break;
            case SFIXED32: ldField.addSize(length*sfixed32Size); break;
            // variable size types
            case INT64   : computeInt64sSize(field, ldField); break;
            case INT32   : computeInt32sSize(field, ldField); break;
            case UINT64  : computeUInt64sSize(field, ldField); break;
            case UINT32  : computeUInt32sSize(field, ldField); break;
            case SINT64  : computeSInt64sSize(field, ldField); break;
            case SINT32  : computeSInt32sSize(field, ldField); break;
            case BOOL    : computeBoolsSize(field, ldField); break;
            case ENUM    : computeEnumsSize(field, ldField); break;
            default:
              throw new IllegalArgumentException("type of the packed field is not primitive");

        }
    }

    public void write(DynamicData dd,  FieldDescriptor fd,
             LengthDelimitedField ldField,
             CodedOutputStream output) throws IOException {

        int id = fd.getNumber();
        DynamicData field = dd.getComplexValue(id);

        int length = field.getLength();
        if(length == 0) return;

        switch(fd.getLiteType()){
            // fixed size types
            case DOUBLE  : writeDoubles(field, id, output); break;
            case FLOAT   : writeFloats(field, id, output);  break;
            case FIXED64 : writeFixed64s(field, id, output); break;
            case FIXED32 : writeFixed32s(field, id, output); break;
            case SFIXED64: writeSFixed64s(field, id, output); break;
            case SFIXED32: writeSFixed32s(field, id, output); break;
            // variable size types
            case INT64   : writeInt64s(field, id, output); break;
            case INT32   : writeInt32s(field, id, output); break;
            case UINT64  : writeUInt64s(field, id, output); break;
            case UINT32  : writeUInt32s(field, id, output); break;
            case SINT64  : writeSInt64s(field, id, output); break;
            case SINT32  : writeSInt32s(field, id, output); break;
            case BOOL    : writeBools(field, id, output); break;
            case ENUM    : writeEnums(field, id, output); break;
            default:
              throw new IllegalArgumentException("type of the packed field is not primitive");
        }
    }

    private void writeDoubles(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeDouble(id, dd.getFloat64Value(i));
        }
    }

    private void writeFloats(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeFloat(id, dd.getFloat32Value(i));
        }
    }

    private void writeFixed64s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeFixed64(id, dd.getInt64Value(i));
        }
    }

    private void writeFixed32s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeFixed32(id, dd.getInt32Value(i));
        }
    }

    private void writeSFixed64s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeSFixed64(id, dd.getInt64Value(i));
        }
    }

    private void writeSFixed32s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeSFixed32(id, dd.getInt32Value(i));
        }
    }

    private void computeInt64sSize(DynamicData dd, 
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeInt64SizeNoTag(dd.getInt64Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeInt64s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write tag-values
        for (int i=0; i < length; i++) {
          output.writeInt64(id, dd.getInt64Value(i));
        }
    }

    private void computeInt32sSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeInt32SizeNoTag(dd.getInt32Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeInt32s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeInt32(id, dd.getInt32Value(i));
        }
    }

    private void computeUInt64sSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeUInt64SizeNoTag(dd.getInt64Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeUInt64s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeUInt64(id, dd.getInt64Value(i));
        }
    }

    private void computeUInt32sSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeUInt32SizeNoTag(dd.getInt32Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeUInt32s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeUInt32(id, dd.getInt32Value(i));
        }
    }

    private void computeSInt64sSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeSInt64SizeNoTag(dd.getInt64Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeSInt64s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeSInt64(id, dd.getInt64Value(i));
        }
    }

    private void computeSInt32sSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeSInt32SizeNoTag(dd.getInt32Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeSInt32s(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeSInt32(id, dd.getInt32Value(i));
        }
    }

    private void computeBoolsSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeBoolSizeNoTag(dd.getBooleanValue(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeBools(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeBool(id, dd.getBooleanValue(i));
        }
    }

   private void computeEnumsSize(DynamicData dd,
            LengthDelimitedField ldField){

        int length = dd.getLength();
        int byteSize = 0;

        // elements
        for (int i=0; i < length; i++) {
          byteSize += CodedOutputStream.computeEnumSizeNoTag(dd.getInt32Value(i));
        }

        ldField.addSize(byteSize);
    }

    private void writeEnums(DynamicData dd, int id,
            CodedOutputStream output) throws IOException {

        int length = dd.getLength();

        // write data
        for (int i=0; i < length; i++) {
          output.writeEnum(id, dd.getInt32Value(i));
        }
    }

    public class PackedField implements LengthDelimitedField {

        private int byteSize;

        public PackedField() {
        }

        public int getSize(){
            return byteSize;
        }

        public void addSize(int size){
            byteSize += size;
        }

        public void addField(int id, LengthDelimitedField ldField){
            throw new java.lang.UnsupportedOperationException();
        }

        public LengthDelimitedField getField(int id){
            throw new java.lang.UnsupportedOperationException();
        }
    }

    static private int doubleSize = CodedOutputStream.computeDoubleSizeNoTag(0.0);
    static private int floatSize = CodedOutputStream.computeFloatSizeNoTag(0);
    static private int fixed64Size = CodedOutputStream.computeFixed64SizeNoTag(0);
    static private int fixed32Size = CodedOutputStream.computeFixed32SizeNoTag(0);
    static private int sfixed64Size = CodedOutputStream.computeSFixed64SizeNoTag(0);
    static private int sfixed32Size = CodedOutputStream.computeSFixed32SizeNoTag(0);

}
